/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import http from '@ohos.net.http';
import dataStorage from '@ohos.data.storage'
import featureAbility from '@ohos.ability.featureAbility'
import config from '../config.js'
import {HuaweiIdAuthParamsHelper, HuaweiIdAuthManager, Scope} from '@hmscore/hms-jsb-account'

const TAG = 'JS/Connection/HuaweiAccount: '
const TokenURL = 'https://oauth-login.cloud.huawei.com/oauth2/v3/token'
const TokenCheckURL = 'https://oauth-api.cloud.huawei.com/rest.php?nsp_fmt=JSON&nsp_svc=huawei.oauth2.user.getTokenInfo'
const REFRESH_TOKEN_EXPIRE_DATE = 180
const LOGIN_ERROR_CODE = {
  FAILED: -1,
  SIGN_IN_AUTH: 2002,
  SIGN_IN_NETWORK_ERROR: 2005,
  SIGN_IN_AUTH_SERVER_FAILED: 2009,
  SIGN_IN_CANCELLED: 2012
};
const ResultCode = {
  AUTHORIZATION_CODE_EFFECT: '11001',
  ACCESS_TOKEN_EFFECT: '11002',
  REFRESH_TOKEN_EFFECT: '11003',
}

function formBody(body) {
  return Object.keys(body).map(property => {
    var encodedKey = encodeURIComponent(property);
    var encodedValue = encodeURIComponent(body[property]);
    return encodedKey + '=' + encodedValue
  }).join('&')
}

function signInError(error) {
  switch (error.errCode) {
    case LOGIN_ERROR_CODE.SIGN_IN_CANCELLED:
      break;
    case LOGIN_ERROR_CODE.SIGN_IN_AUTH:
    case LOGIN_ERROR_CODE.FAILED:
      error.showMsg = 'strings.public_logged_out'
      break;
    case LOGIN_ERROR_CODE.SIGN_IN_NETWORK_ERROR:
    case LOGIN_ERROR_CODE.SIGN_IN_AUTH_SERVER_FAILED:
      error.showMsg = 'strings.public_network_error'
      break;
    default:
      error.showMsg = 'strings.public_system_error'
  }
  return error
}

function RESTError(e) {
  // 错误码: https://developer.huawei.com/consumer/cn/doc/development/HMSCore-References/server-error-codes-0000001062371380
  console.error(TAG + 'restyError' + JSON.stringify(e))
  let data = JSON.parse(e.result)
  if (e.responseCode == 400) {
    if (data.error == 6 || data.error == 102) {
      data.errorCode = ResultCode.ACCESS_TOKEN_EFFECT
    } else if (data.error == 1101 && (data.sub_error == 20155 || data.sub_error == 20156)) {
      data.errorCode = ResultCode.AUTHORIZATION_CODE_EFFECT
    } else if (data.error == 1203 && (data.sub_error == 11205 || data.sub_error == 31204)) {
      data.errorCode = ResultCode.REFRESH_TOKEN_EFFECT
    }
  }
  return data
}

function resolveAuthId(authId) {
  let resultObj = {}
  let scopes = config.mandatoryScopeList.map(scopeUri => new Scope(scopeUri));
  resultObj.allScopeGranted = HuaweiIdAuthManager.containScopes(authId, scopes);
  resultObj.authCode = authId.serverAuthCode;
  resultObj.unionId = authId.unionId;
  resultObj.openId = authId.openId;
  return resultObj;
}

function performSignIn() {
  return new Promise((resolve, reject) => {
    let scopes = config.requireScopeList.map(scopeUri => new Scope(scopeUri))
    let signInOption = new HuaweiIdAuthParamsHelper().setId()
      .setProfile()
      .setScopeList(scopes)
      .setAuthorizationCode()
      .setDialogAuth()
      .build();
    HuaweiIdAuthManager.getAuthApi()
      .getSignInIntent(signInOption)
      .then(resolve)
      .catch(result => reject(signInError(result)));
  })
}

function performSilentSignIn() {
  return new Promise((resolve, reject) => {
    let scopes = config.mandatoryScopeList.map(scopeUri => new Scope(scopeUri));
    let signInOption = new HuaweiIdAuthParamsHelper()
    .setId()
      .setProfile()
      .setScopeList(scopes)
      .setAuthorizationCode()
      .build();
    HuaweiIdAuthManager.getAuthApi()
      .silentSignIn(signInOption)
      .then(resolve)
      .catch(result => reject(signInError(result)));
  })
}

function getAccessToken(authId) {
  return new Promise((resolve, reject) => {
    let loginResult = resolveAuthId(authId);
    let body = {
      grant_type: 'authorization_code',
      client_id: config.clientId,
      client_secret: config.clientSecret,
      redirect_uri: config.redirectUri,
      code: loginResult.authCode
    }
    let httpRequest = http.createHttp();
    httpRequest.request(TokenURL,
      {
        method: 'POST',
        header: {
          'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
        },
        extraData: formBody(body),
      }).then(data => {
      if (data.responseCode == 200) {
        console.debug(TAG + 'Get access token success!');
        let tokenResult = JSON.parse(data.result);
        loginResult.accessToken = tokenResult.access_token;
        loginResult.refreshToken = tokenResult.refresh_token;
        saveRefreshToken(loginResult.refreshToken)
        resolve(loginResult);
      } else {
        reject(RESTError(data));
      }
    }).catch(err => {
      reject(err);
    })
  });
}

async function saveRefreshToken(token) {
  console.info(TAG + 'Start saving Refresh Token data.');
  let date = new Date();
  date.setDate(date.getDate() + REFRESH_TOKEN_EXPIRE_DATE);
  // Refresh token is valid in 180 days. However logout/auth cancel may revoke the token.
  var context = featureAbility.getContext();
  var path = await context.getFilesDir();
  let storage = dataStorage.getStorageSync(path + '/preferences')
  storage.putSync('rt_expire_timestamp', date.getTime().toString())
  storage.putSync('refresh_token', token)
  storage.flushSync();
}

export default {
  signIn() {
    return performSignIn().then(getAccessToken);
  },
  silentSignIn() {
    return performSilentSignIn().then(getAccessToken);
  },
  checkScope(accessToken, scopeToCheck){
    return new Promise((resolve, reject)=>{
      console.error('Start check scope')
      let body = {
        access_token: accessToken,
      }
      let httpRequest = http.createHttp();
      httpRequest.request(TokenCheckURL,
        {
          method: 'POST',
          header: {
            'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
          },
          extraData: formBody(body),
        }).then(data => {
        if (data.responseCode == 200) {
          console.debug(TAG + 'Resolve access token success!' + data.result);
          let tokenResult = JSON.parse(data.result);
          let scopeList = tokenResult.scope.split(' ');
          if (scopeList.some(scope => scope == scopeToCheck)) {
            resolve(true);
          } else {
            let err = new Error('ScopeError')
            reject(err);
          }
        } else {
          let err = new Error('Rest Error')
          err.message = data.result;
          reject(err);
        }
      }).catch(err => {
        reject(err);
      });
    });
  },
  requestSingleScope(scope){
    return new Promise((resolve, reject) => {
      let signInOption = new HuaweiIdAuthParamsHelper().setId()
        .setScope(new Scope(scope))
        .setAuthorizationCode()
        .setDialogAuth()
        .build();
      HuaweiIdAuthManager.getAuthApi()
        .getSignInIntent(signInOption)
        .then(resolve)
        .catch(result => reject(signInError(result)));
    }).then(getAccessToken)
  },
  async refreshAccessToken(successCallback, failCallback) {
    var context = featureAbility.getContext();
    var path = await context.getFilesDir();
    let storage = dataStorage.getStorageSync(path + '/preferences');
    let refreshToken = storage.getSync('refresh_token', '');
    if (refreshToken == '') {
      return;
    }
    let httpRequest = http.createHttp();
    let body = {
      grant_type: 'refresh_token',
      client_id: config.clientId,
      client_secret: config.clientSecret,
      refresh_token: refreshToken
    }
    httpRequest.request(TokenURL,
      {
        method: 'POST',
        header: {
          'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
        },
        extraData: formBody(body),
      }).then(data => {
      if (data.responseCode == 200) {
        successCallback(JSON.parse(data.result));
      } else {
        failCallback(RESTError(data), data.responseCode)
      }
    }).catch(err => {
      failCallback(err.message, err.code)
    })
  }
}